GIE = 1; // global interrupts enable
PBIE = 1; // portb change interrupt enable
IOCB7 = 1; // only pin 7 should trigger interrupt
IOCB6 = 0;
IOCB5 = 0;
IOCB4 = 0;
PBIF = 0; // reset any portb interrupt
While I got the interrupt working, clearing it would not work properly, the interrupt just refired instantly.
A little googling found me application note AN566 from microchip which solved the mystery: after the interrupt has been triggered, portb must be read to reset the mismatch state.
It may be possible to read the values into any variable, I haven't tried this yet. The solution used in the AN is a single assembly statement that reads portb onto itself:
MOVF PORTB, 1
After this, everything works as expected.
NB: the pic18F's (some of them at least) have one or more external interrups, on portb 0-3 on the 18F46k80 for example. It is better to use these if you only need to check that an interrupt has occurred. The external interrups are edge triggered, so they will only trigger on EITHER the rising OR the falling edge. Portb change interrups trigger on both.